package ferp.core.player;

import ferp.core.ai.strategy.Strategy;
import ferp.core.ai.strategy.simple.Simulate;
import ferp.core.game.Bid;
import ferp.core.game.Game;
import ferp.core.game.Input;
import ferp.core.game.Settings;
import ferp.core.state.State;

/**
 * User: olegs
 * Date: 19/05/11 15:12
 */
public class AI extends Robot
{
  public AI(Game g, int id, String name, Strategy strategy)
  {
    super(g, id, name);

    this.strategy = strategy;
  }

  @Override
  public int setBiddingInputOptions(Game g, Input.Options io) {return State.Result.SCHEDULE;}

  @Override
  public int setDroppingInputOptions(Game g, Settings settings, Input input, Input.Options io)
  {
    if (canWithout3(g, settings) && strategy.dropping().isWithout3(g, this, settings))
      input.bid = Bid.without3();

    return State.Result.SCHEDULE;
  }

  @Override
  public int setContractingInputOptions(Game g, Input.Options io) {return State.Result.SCHEDULE;}

  @Override
  public int setContractingInputOptionsCheck10(Input.Options io)
  {
    io.set(Input.Options.TRICKING_10_CHECK);

    return State.Result.SCHEDULE;
  }

  @Override
  public int setStandInputOptions(Input.Options io) {return State.Result.SCHEDULE;}

  // this method should not be called if the player passed in the previous bidding round
  @Override
  public void bid(Game.Listener listener, Game g, Settings settings, Input input)
  {
    // for misere debugging
/*
    if (id() == 1)
    {
      g.setCurrentBid(id(), Bid.misere());
      g.setMaxBid(id(), Bid.misere());
      return;
    }
*/

    // for tricking debugging
/*
    if (id() == 2)
    {
      g.setCurrentBid(id(), Bid.play(Card.Suit.SPADES, 10));
      g.setMaxBid(id(), Bid.play(Card.Suit.SPADES, 10));
      return;
    }
*/

    // determine maximal bid only once
    if (g.getBid(id()).max == null)
      g.setMaxBid(id(), strategy.bidding().bid(g, settings, this, g.options.bid));

    // get maximal bid
    Bid max = g.getBid(id()).max;

    // check for misere
    if (max.isMisere())
    {
      if (g.options.isSet(Input.Options.MISERE))
        g.setCurrentBid(id(), max);
      else
        g.setCurrentBid(id(), Bid.pass());
    }
    else
    {
      // bid incrementally
      if (max.equals(g.options.bid) || max.greater(g.options.bid) || strategy.bidding().risk(g, this, max))
        g.setCurrentBid(id(), new Bid(g.options.bid));
      else
      {
        // in case no there were bids yet and it's better to play - say "play" even if we'll have to say "without3" later
        if (g.bid == null)
        {
          long pass = Simulate.pass(g, settings, this);

          if (Simulate.play(g, settings, this, g.options.bid, max) > pass || Simulate.without3(g, settings, this, g.options.bid, max) > pass)
            g.setCurrentBid(id(), new Bid(g.options.bid));
          else
            g.setCurrentBid(id(), Bid.pass());
        }
        else
          g.setCurrentBid(id(), Bid.pass());
      }
    }
  }

  @Override
  public void drop(Game g, Input input)
  {
    if (g.getBid(id()).current.isMisere())
      strategy.dropping().misere(g, this);
    else
      strategy.dropping().tricks(g.getHand(id()).current, isFirstHand(g), g.sets, g.drop());
  }

  @Override
  public void contract(Game g, Settings settings, Input input)
  {
    if (g.options.isSet(Input.Options.TRICKING_10_CHECK))
    {
      Bid bid = Bid.pass();

      g.setBid(id(), bid, bid);
    }
    else
      if (g.options.isSet(Input.Options.WHIST_PASS) || g.options.isSet(Input.Options.WHIST_HALF) || g.options.isSet(Input.Options.WHIST_FULL))
        contractWhist(g, settings);
      else
        contractTricks(g);
  }

  @Override
  public void setGameType(Game g, Input input)
  {
    // basing on the current strategy
    g.type = strategy.tricking().type(g);
  }

  @Override
  public void setCheck10(Game g, Input input)
  {
    if (g.declarer() == g.human())
      g.check10 = true;
    // otherwise AI shouldn't touch this flag - only human opponent may change it
  }

  public Strategy strategy() {return strategy;}

  private Strategy strategy;

  // make contract for tricking game when declaring
  private void contractTricks(Game g)
  {
    Bid contract = strategy.contracting().tricks(g.getHand(id()).current, isFirstHand(g));

    // ensure the contract is not less that the last declared did
    contract.adjust(g.bid);
    g.setBid(id(), contract, contract);
  }

  // try make contract for tricking game when whisting
  private void contractWhist(Game g, Settings settings)
  {
    Bid whists = strategy.contracting().whists(g, settings, this);

    g.setBid(id(), whists, whists);
  }

  /*
  private List<Card> reverse(Card.Suit s)
  {
    Iterator<Card> i = cards(s).iterator();
    List<Card> reversed = new LinkedList<Card>();

    while (i.hasNext())
      reversed.add(0, i.next());

    return reversed;
  }

  private int tricks(List<Card> list)
  {
    int rsum = 0, expected = Rank.ACE.ordinal(), gaps = 0, dists = 0, count = 0, hash = 0, tricks = 0;

    // list is reversed
    for (Card c : list)
    {
      int rank = c.rank().ordinal();
      int diff = expected - rank;

      ++count;

      rsum += rank;
      gaps += diff;
      dists += diff;
      hash += (1 << rank);

      if (dists > 0)
      {
        --dists;

        if (count > 3)
          ++tricks;
      }
      else
        ++tricks;

      expected = rank - 1;
    }

    // System.out.println(" rsum=" + rsum + ", gaps=" + gaps + ", hash=" + hash + ", tricks=" + tricks);

    return tricks;
  }

  public static int tricks(byte hash)
  {
    int expected = 7, dists = 0, tricks = 0;
    int remaining = 8 - size(hash);

    // list is reversed
    for (int rank = 7; rank >= 0; --rank)
    {
      if ((hash & (1 << rank)) != 0)
      {
        int diff = expected - rank;

        --remaining;

        dists += diff;

        if (dists == 0 || remaining <= 0)
          ++tricks;
        else
          --dists;

        expected = rank - 1;
      }
    }

    return tricks;
  }

  public static int size(int hash)
  {
    int size = 0;

    for (int rank = 7; rank >= 0; --rank)
      if ((hash & (1 << rank)) != 0)
        ++size;

    return size;
  }
  */
}
